import math
from datetime import datetime

import pandas as pd
import numpy as np

# ------------------ 0级：核心工具函数 --------------------------------------------
from db.models import StockLifeline
from utils.CommonEnum import TrendEnum, LifelineTypeEnum, LineModeEnum, LinePositionEnum
from utils.util import CommonUtil


class BaseToolUtil:

    @staticmethod
    def RD(N, D=3):
        '''
        四舍五入取3位小数
        :param N: 数组
        :param D: 小数位
        :return:
        '''
        return np.round(N, D)

    @staticmethod
    def RET(S, N=1):
        '''
        返回序列倒数第N个值,默认返回最后一个
        :param S:
        :param N:
        :return:
        '''
        return np.array(S)[-N]

    @staticmethod
    def ABS(S):
        '''
         返回N的绝对值
        :param S:
        :return:
        '''
        return np.abs(S)

    @staticmethod
    def MAX(S1, S2):
        '''
        返回两序列里最大的值
        :param S1:
        :param S2:
        :return:
        '''
        return np.maximum(S1, S2)

    @staticmethod
    def MIN(S1, S2):
        '''
        序列min
        :param S1:
        :param S2:
        :return:
        '''
        return np.minimum(S1, S2)

    @staticmethod
    def MA(S, N):
        '''
         求序列的N日平均值，返回序列
        :param S:
        :param N:
        :return:
        '''
        return pd.Series(S).rolling(N).mean().bfill()

    @staticmethod
    def REF(S, N=1):
        '''
        对序列整体下移动N,返回序列(shift后会产生NAN)
        :param S:
        :param N:
        :return:
        '''
        return pd.Series(S).shift(N).values

    @staticmethod
    def DIFF(S, N=1):
        '''
        前一个值减后一个值,前面会产生nan
        :param S:
        :param N:
        :return:
        '''
        return pd.Series(S).diff(N)  # np.diff(S)直接删除nan，会少一行

    @staticmethod
    def STD(S, N):
        '''
        求序列的N日标准差，返回序列
        '''
        return pd.Series(S).rolling(N).std(ddof=0).values

    @staticmethod
    def IF(S_BOOL, S_TRUE, S_FALSE):
        '''
        序列布尔判断 res=S_TRUE if S_BOOL==True  else  S_FALSE
        '''
        return np.where(S_BOOL, S_TRUE, S_FALSE)

    @staticmethod
    def SUM(S, N):
        '''
        对序列求N天累计和，返回序列
        '''
        return pd.Series(S).rolling(N).sum().values

    @staticmethod
    def HHV(S, N):
        '''
         HHV(C, 5)  # 最近5天收盘最高价
        '''
        return pd.Series(S).rolling(N).max().values

    @staticmethod
    def LLV(S, N):
        '''
         LLV(C, 5)  # 最近5天收盘最低价
        '''
        return pd.Series(S).rolling(N).min().values

    @staticmethod
    def EMA(S, N):
        '''
        指数移动平均,为了精度 S>4*N  EMA至少需要120周期
        '''
        return pd.Series(S).ewm(span=N, adjust=False).mean().values

    @staticmethod
    def SMA(S, N, M=1):
        '''
        中国式的SMA,至少需要120周期才精确
        '''
        K = pd.Series(S).rolling(N).mean()  # 先求出平均值 (下面如果有不用循环的办法，能提高性能，望告知)
        for i in range(N + 1, len(S)):
            K[i] = (M * S[i] + (N - M) * K[i - 1]) / N  # 因为要取K[i-1]，所以 range(N+1, len(S))
        return K

    @staticmethod
    def AVEDEV(S, N):
        '''
        平均绝对偏差  (序列与其平均值的绝对差的平均值)
        '''
        avedev = pd.Series(S).rolling(N).apply(lambda x: (np.abs(x - x.mean())).mean())
        return avedev.values

    @staticmethod
    def SLOPE(S, N=5, RS=False):
        '''
        返S序列N周期回线性回归斜率 (默认只返回斜率,不返回整个直线序列)
        '''
        M = pd.Series(S[-N:])
        poly = np.polyfit(M.index, M.values, deg=1)
        Y = np.polyval(poly, M.index)
        if RS: return Y[1] - Y[0], Y
        return Y[1] - Y[0]


# ------------------   1级：应用层函数(通过0级核心函数实现） ----------------------------------
class ApplicationUtil:

    @staticmethod
    def COUNT(S_BOOL, N):
        '''
        COUNT(CLOSE>O, N):  最近N天满足S_BOO的天数  True的天数
        '''
        return BaseToolUtil.SUM(S_BOOL, N)

    @staticmethod
    def EVERY(S_BOOL, N):
        '''
        EVERY(CLOSE>O, 5)   最近N天是否都是True
        '''
        R = BaseToolUtil.SUM(S_BOOL, N)
        return BaseToolUtil.IF(R == N, True, False)

    @staticmethod
    def LAST(S_BOOL, A, B):
        '''
        从前A日到前B日一直满足S_BOOL条件
        '''
        if A < B: A = B  # 要求A>B    例：LAST(CLOSE>OPEN,5,3)  5天前到3天前是否都收阳线
        return S_BOOL[-A:-B].sum() == (A - B)  # 返回单个布尔值

    @staticmethod
    def EXIST(S_BOOL, N=5):
        '''
        EXIST(CLOSE>3010, N=5)  n日内是否存在一天大于3000点
        '''
        R = BaseToolUtil.SUM(S_BOOL, N)
        return BaseToolUtil.IF(R > 0, True, False)

    @staticmethod
    def BARSLAST(S_BOOL):
        '''
        上一次条件成立到当前的周期
        '''
        M = np.argwhere(S_BOOL)  # BARSLAST(CLOSE/REF(CLOSE)>=1.1) 上一次涨停到今天的天数
        return len(S_BOOL) - int(M[-1]) - 1 if M.size > 0 else -1

    @staticmethod
    def FORCAST(S, N):
        '''
        返S序列N周期回线性回归后的预测值
        '''
        K, Y = BaseToolUtil.SLOPE(S, N, RS=True)
        return Y[-1] + K

    @staticmethod
    def CROSS(S1, S2):
        '''
        判断穿越 CROSS(MA(C,5),MA(C,10))
        '''
        CROSS_BOOL = BaseToolUtil.IF(S1 > S2, True, False)
        return (ApplicationUtil.COUNT(CROSS_BOOL > 0, 2) == 1) & (CROSS_BOOL == True)  # 上穿：昨天0 今天1   下穿：昨天1 今天0


# ------------------   2级：技术指标函数(全部通过0级，1级函数实现） ------------------------------
class TechnicalIndexUtil:

    @staticmethod
    def MACD(CLOSE, SHORT=12, LONG=26, M=9):
        '''
        EMA的关系，S取120日，和雪球小数点2位相同
        '''
        DIF = BaseToolUtil.EMA(CLOSE, SHORT) - BaseToolUtil.EMA(CLOSE, LONG)
        DEA = BaseToolUtil.EMA(DIF, M)
        MACD = (DIF - DEA) * 2
        return BaseToolUtil.RD(DIF), BaseToolUtil.RD(DEA), BaseToolUtil.RD(MACD)

    @staticmethod
    def KDJ(CLOSE, HIGH, LOW, N=9, M1=3, M2=3):
        '''
        KDJ指标
        '''
        RSV = (CLOSE - BaseToolUtil.LLV(LOW, N)) / (BaseToolUtil.HHV(HIGH, N) - BaseToolUtil.LLV(LOW, N)) * 100
        K = BaseToolUtil.EMA(RSV, (M1 * 2 - 1))
        D = BaseToolUtil.EMA(K, (M2 * 2 - 1))
        J = K * 3 - D * 2
        return K, D, J

    @staticmethod
    def RSI(CLOSE, N=24):
        DIF = CLOSE - BaseToolUtil.REF(CLOSE, 1)
        return BaseToolUtil.RD(
            BaseToolUtil.SMA(BaseToolUtil.MAX(DIF, 0), N) / BaseToolUtil.SMA(BaseToolUtil.ABS(DIF), N) * 100)

    @staticmethod
    def WR(CLOSE, HIGH, LOW, N=10, N1=6):
        '''
        W&R 威廉指标
        '''
        WR = (BaseToolUtil.HHV(HIGH, N) - CLOSE) / (BaseToolUtil.HHV(HIGH, N) - BaseToolUtil.LLV(LOW, N)) * 100
        WR1 = (BaseToolUtil.HHV(HIGH, N1) - CLOSE) / (BaseToolUtil.HHV(HIGH, N1) - BaseToolUtil.LLV(LOW, N1)) * 100
        return BaseToolUtil.RD(WR), BaseToolUtil.RD(WR1)

    @staticmethod
    def BIAS(CLOSE, L1=6, L2=12, L3=24):
        '''
        BIAS乖离率
        '''
        BIAS1 = (CLOSE - BaseToolUtil.MA(CLOSE, L1)) / BaseToolUtil.MA(CLOSE, L1) * 100
        BIAS2 = (CLOSE - BaseToolUtil.MA(CLOSE, L2)) / BaseToolUtil.MA(CLOSE, L2) * 100
        BIAS3 = (CLOSE - BaseToolUtil.MA(CLOSE, L3)) / BaseToolUtil.MA(CLOSE, L3) * 100
        return BaseToolUtil.RD(BIAS1), BaseToolUtil.RD(BIAS2), BaseToolUtil.RD(BIAS3)

    @staticmethod
    def BOLL(CLOSE, N=20, P=2):
        '''
         BOLL指标，布林带
        '''
        MID = BaseToolUtil.MA(CLOSE, N)
        UPPER = MID + BaseToolUtil.STD(CLOSE, N) * P
        LOWER = MID - BaseToolUtil.STD(CLOSE, N) * P
        return BaseToolUtil.RD(UPPER), BaseToolUtil.RD(MID), BaseToolUtil.RD(LOWER)

    @staticmethod
    def PSY(CLOSE, N=12, M=6):
        PSY = ApplicationUtil.COUNT(CLOSE > BaseToolUtil.REF(CLOSE, 1), N) / N * 100
        PSYMA = BaseToolUtil.MA(PSY, M)
        return BaseToolUtil.RD(PSY), BaseToolUtil.RD(PSYMA)

    @staticmethod
    def CCI(CLOSE, HIGH, LOW, N=14):
        TP = (HIGH + LOW + CLOSE) / 3
        return (TP - BaseToolUtil.MA(TP, N)) / (0.015 * BaseToolUtil.AVEDEV(TP, N))

    @staticmethod
    def ATR(CLOSE, HIGH, LOW, N=20):
        '''
        真实波动N日平均值
        '''
        TR = BaseToolUtil.MAX(BaseToolUtil.MAX((HIGH - LOW), BaseToolUtil.ABS(BaseToolUtil.REF(CLOSE, 1) - HIGH)),
                              BaseToolUtil.ABS(BaseToolUtil.REF(CLOSE, 1) - LOW))
        return BaseToolUtil.MA(TR, N)

    @staticmethod
    def BBI(CLOSE, M1=3, M2=6, M3=12, M4=20):
        '''
        BBI多空指标
        '''
        return (BaseToolUtil.MA(CLOSE, M1) + BaseToolUtil.MA(CLOSE, M2) + BaseToolUtil.MA(CLOSE, M3) + BaseToolUtil.MA(
            CLOSE, M4)) / 4

    @staticmethod
    def DMI(CLOSE, HIGH, LOW, M1=14, M2=6):
        '''
        动向指标：结果和同花顺，通达信完全一致
        '''
        TR = BaseToolUtil.SUM(
            BaseToolUtil.MAX(BaseToolUtil.MAX(HIGH - LOW, BaseToolUtil.ABS(HIGH - BaseToolUtil.REF(CLOSE, 1))),
                             BaseToolUtil.ABS(LOW - BaseToolUtil.REF(CLOSE, 1))), M1)
        HD = HIGH - BaseToolUtil.REF(HIGH, 1)
        LD = BaseToolUtil.REF(LOW, 1) - LOW
        DMP = BaseToolUtil.SUM(BaseToolUtil.IF((HD > 0) & (HD > LD), HD, 0), M1)
        DMM = BaseToolUtil.SUM(BaseToolUtil.IF((LD > 0) & (LD > HD), LD, 0), M1)
        PDI = DMP * 100 / TR
        MDI = DMM * 100 / TR
        ADX = BaseToolUtil.MA(BaseToolUtil.ABS(MDI - PDI) / (PDI + MDI) * 100, M2)
        ADXR = (ADX + BaseToolUtil.REF(ADX, M2)) / 2
        return PDI, MDI, ADX, ADXR

    @staticmethod
    def TAQ(HIGH, LOW, N):
        '''
        唐安奇通道交易指标，大道至简，能穿越牛熊
        '''
        UP = BaseToolUtil.HHV(HIGH, N)
        DOWN = BaseToolUtil.LLV(LOW, N)
        MID = (UP + DOWN) / 2
        return UP, MID, DOWN

    @staticmethod
    def TRIX(CLOSE, M1=12, M2=20):
        '''
        三重指数平滑平均线
        '''
        TR = BaseToolUtil.EMA(BaseToolUtil.EMA(BaseToolUtil.EMA(CLOSE, M1), M1), M1)
        TRIX = (TR - BaseToolUtil.REF(TR, 1)) / BaseToolUtil.REF(TR, 1) * 100
        TRMA = BaseToolUtil.MA(TRIX, M2)
        return TRIX, TRMA

    @staticmethod
    def VR(CLOSE, VOL, M1=26):
        '''
        VR容量比率
        '''
        LC = BaseToolUtil.REF(CLOSE, 1)
        return BaseToolUtil.SUM(BaseToolUtil.IF(CLOSE > LC, VOL, 0), M1) / BaseToolUtil.SUM(
            BaseToolUtil.IF(CLOSE <= LC, VOL, 0), M1) * 100

    @staticmethod
    def EMV(HIGH, LOW, VOL, N=14, M=9):
        '''
        简易波动指标
        '''
        VOLUME = BaseToolUtil.MA(VOL, N) / VOL
        MID = 100 * (HIGH + LOW - BaseToolUtil.REF(HIGH + LOW, 1)) / (HIGH + LOW)
        EMV = BaseToolUtil.MA(MID * VOLUME * (HIGH - LOW) / BaseToolUtil.MA(HIGH - LOW, N), N)
        MAEMV = BaseToolUtil.MA(EMV, M)
        return EMV, MAEMV

    @staticmethod
    def DPO(CLOSE, M1=20, M2=10, M3=6):
        '''
        区间震荡线
        '''
        DPO = CLOSE - BaseToolUtil.REF(BaseToolUtil.MA(CLOSE, M1), M2)
        MADPO = BaseToolUtil.MA(DPO, M3)
        return DPO, MADPO

    @staticmethod
    def BRAR(OPEN, CLOSE, HIGH, LOW, M1=26):
        '''
        BRAR-ARBR 情绪指标
        '''
        AR = BaseToolUtil.SUM(HIGH - OPEN, M1) / BaseToolUtil.SUM(OPEN - LOW, M1) * 100
        BR = BaseToolUtil.SUM(BaseToolUtil.MAX(0, HIGH - BaseToolUtil.REF(CLOSE, 1)), M1) / BaseToolUtil.SUM(
            BaseToolUtil.MAX(0, BaseToolUtil.REF(CLOSE, 1) - LOW), M1) * 100
        return AR, BR

    @staticmethod
    def DMA(CLOSE, N1=10, N2=50, M=10):
        '''
        平行线差指标
        '''
        DIF = BaseToolUtil.MA(CLOSE, N1) - BaseToolUtil.MA(CLOSE, N2)
        DIFMA = BaseToolUtil.MA(DIF, M)
        return DIF, DIFMA

    @staticmethod
    def MTM(CLOSE, N=12, M=6):
        '''
        动量指标
        '''
        MTM = CLOSE - BaseToolUtil.REF(CLOSE, N)
        MTMMA = BaseToolUtil.MA(MTM, M)
        return MTM, MTMMA

    @staticmethod
    def ROC(CLOSE, N=12, M=6):
        '''
        变动率指标
        '''
        ROC = 100 * (CLOSE - BaseToolUtil.REF(CLOSE, N)) / BaseToolUtil.REF(CLOSE, N)
        MAROC = BaseToolUtil.MA(ROC, M)
        return ROC, MAROC


class Extremum:
    '''
    极值
    '''

    @staticmethod
    def highest(pd_data, oper_col, startDate, endDate):
        '''
        获取区间内最高价及日期
        :param pd_data: pd数据集
        :param oper_attr: 操作列属性名称
        :param startDate: 开始时间
        :param endDate: 结束时间
        :return: (符合条件的索引,符合条件的值)
        '''
        slc = slice(pd.Timestamp(startDate), pd.Timestamp(endDate))
        slc_ = pd_data[slc]

        try:
            max_idx = slc_[oper_col].astype('float64').idxmax()
            max_val = pd_data[oper_col].loc[max_idx]
        except Exception as e:
            print(pd_data)

        return max_idx, max_val

    @staticmethod
    def highest1(pd_series, period):
        '''
        获取最近周期最高价及日期
        :param pd_series: pd数据集
        :param period: 周期
        :return: (符合条件的索引,符合条件的值)
        '''

        max_idx = pd_series.tail(period).idxmax()
        max_val = pd_series.tail(period).max()
        return max_idx, max_val

    @staticmethod
    def highest_lately(S, N):
        '''
        滚动近N日最高值,前N个NAN值被替换为最大值
        '''
        return S.rolling(N).max().bfill()

    @staticmethod
    def lowest_lately(S, N):
        '''
        近N日最低值,前N个NAN值被替换为最小值
        '''
        return S.rolling(N).min().bfill()

    @staticmethod
    def stage_highs_lows(pd_data, column, window=20):
        '''
        计算阶段高低点
        '''
        df_temp = pd.DataFrame(pd_data[column])

        df_temp.loc[:, 'close_1'] = df_temp[column].shift(-1)
        df_temp.loc[:, 'close_2'] = df_temp[column].shift(-2)
        # 30日最高
        df_temp.loc[:, 'max_30'] = df_temp[column].rolling(window, min_periods=1).max()
        df_temp.loc[:, 'min_30'] = df_temp[column].rolling(window, min_periods=1).min()
        df_temp.loc[(df_temp[column] > df_temp['close_1']) & (df_temp[column] >= df_temp['close_2']) & (
                df_temp[column] == df_temp['max_30']), 'k_des'] = 'high'
        df_temp.loc[(df_temp[column] < df_temp['close_1']) & (df_temp[column] <= df_temp['close_2']) & (
                df_temp[column] == df_temp['min_30']), 'k_des'] = 'low'
        df_temp = df_temp.drop(columns=['close_1', 'close_2', 'max_30', 'min_30'])
        return df_temp

    @staticmethod
    def lowest(pd_data, oper_col, startDate, endDate):
        '''
        获取最低价及日期
        :param pd_data: pd数据集
        :param oper_attr: 操作列属性名称
        :param startDate: 开始时间
        :param endDate: 结束时间
        :return: (符合条件的索引,符合条件的值)
        '''
        min_idx = pd_data[oper_col].loc[startDate:endDate].idxmin()
        min_val = pd_data[oper_col].loc[min_idx]
        return min_idx, min_val

    @staticmethod
    def lowest1(pd_series, period):
        '''
        获取最高价及日期
        :param pd_series: pd数据集
        :param period: 周期
        :return: (符合条件的索引,符合条件的值)
        '''

        min_idx = pd_series.tail(period).idxmin()
        min_val = pd_series.tail(period).min()
        return min_idx, min_val


class AverageLine:
    '''
    均线处理
    '''

    def setAverage(self, pd_data, periods):
        CLOSE = pd_data['close']
        if periods:
            for period in periods:
                pd_data.loc[:, f'ma_{period}'] = BaseToolUtil.MA(CLOSE, period)


class VolumeHandle:
    '''
    成交量的一些逻辑处理
    '''

    def __init__(self, pd_data, window=30):
        self.pd_data = pd_data
        self.df_temp = self.pd_data[['close', 'volume']].copy()
        self.window = window

    def judge_trend(self):
        '''
        判断成交量的趋势
        :param pd_data:
        :param window:
        :return:
        '''

        self.df_temp.loc[:, 'ranking'] = self.df_temp['volume'].rolling(self.window).rank()
        self.df_temp.loc[:, 'score'] = self.df_temp['ranking'].map(lambda x: 2 * (x - 1) / self.window - 1)
        self.df_temp.loc[self.df_temp['score'] > 0.8, 'volumn_signal'] = '放量'

    def rising_band(self, pctChg=5):
        '''
        放量上涨5%
        :param pd_data:
        :param window:
        :return:
        '''
        self.judge_trend()
        self.pd_data.loc[
            (self.pd_data['pctChg'] > pctChg) & (self.df_temp['volumn_signal'] == '放量'), 'chg_vol_signal'] = 1
        self.pd_data.loc[
            (self.pd_data['pctChg'] < -pctChg) & (self.df_temp['volumn_signal'] == '放量'), 'chg_vol_signal'] = -1


class TrendHandler:
    '''
    趋势处理
    '''

    def __init__(self, pd_data):
        self.pd_data = pd_data

    def turning_point1(self, column='close', window=20):
        '''
        转折点
        最近20日最高点，最低点 后第三天信号确认
        :param pd_series:
        :return:
        '''

        df_temp = self.pd_data[[column]]
        df_temp = df_temp.copy()
        df_temp.reset_index(inplace=True)
        column_ = df_temp[column]
        df_temp.loc[:, 'close_1'] = column_.shift(1)
        df_temp.loc[:, 'close_2'] = column_.shift(2)
        df_temp.loc[:, 'close_3'] = column_.shift(3)
        # 30日最高
        df_temp.loc[:, 'max_30'] = column_.rolling(window, min_periods=1).max()
        df_temp.loc[:, 'min_30'] = column_.rolling(window, min_periods=1).min()
        for index, row in df_temp.iterrows():
            if index < 3:
                continue
            if df_temp['max_30'].loc[index - 3] == df_temp[column].loc[index - 3]:
                if (df_temp[column].loc[index - 3] > df_temp[column].loc[index - 2]) & (
                        df_temp[column].loc[index - 3] > df_temp[column].loc[index - 1]):
                    # df_temp_1.loc[index-3,'kes']='hign'
                    df_temp.loc[index, 'k_des'] = 'hign'
            elif df_temp['min_30'].loc[index - 3] == df_temp[column].loc[index - 3]:
                if (df_temp[column].loc[index - 3] < df_temp[column].loc[index - 2]) & (
                        df_temp[column].loc[index - 3] < df_temp[column].loc[index - 1]):
                    # df_temp.loc[index-3,'kes']='low'
                    df_temp.loc[index, 'k_des'] = 'low'

        df_temp.set_index('date', inplace=True)
        return self.pd_data.join(df_temp['k_des'])

    @staticmethod
    def __FTTO(P, x_cur, x_index, x_1, x_2, x_3, eligible_index):
        if P < 0:
            if x_3 - x_1 < 0:
                if x_1 >= x_2:
                    eligible_index.append(x_index + 1)
                else:
                    eligible_index.append(x_index + 2)
            elif x_3 - x_1 > 0:
                if x_1 >= x_2:
                    eligible_index.append(x_index + 2)
                else:
                    eligible_index.append(x_index + 1)
        if P == 0:
            if x_3 - x_1 != 0 and x_2 - x_cur == 0:
                eligible_index.append(x_index + 2)
            elif x_3 - x_1 == 0 and x_2 - x_cur != 0:
                eligible_index.append(x_index + 1)

    def trend(self):
        '''
        趋势判断
        :return:
        '''


class ProfitUtil:

    def __init__(self, pd_data, period_short=5, period_medium=10):
        self.pd_data = pd_data
        self.period_short = period_short
        self.period_medium = period_medium
        self.calculate_flag = False

    '''
    计算涨跌幅及收益
    '''

    @staticmethod
    def calculationChg(now_price, base_price):
        '''
        计算涨跌幅
        :param now_price: 现价
        :param base_price: 基准价格
        :return: 涨跌幅(百分数)
        '''
        if not now_price or not base_price:
            return None

        now_price = float(now_price)
        base_price = float(base_price)
        return round((now_price - base_price) / base_price * 100, 2)

    def calculateProfit(self):
        '''

        计算发生信号后的次日成交
        次日成交价以发生信号日的close价为准去购买
        股价涨跌幅以购买日的次日开始计算
        :param pd_data:
        :param period_short:
        :param period_medium:
        :return:
        '''

        pd_temp = self.pd_data.copy()
        # 次日开盘价
        OPEN = pd_temp['open'].shift(-1)
        # 信号发生日的收盘价
        CLOSE = pd_temp['close']
        # 次日成交价以较低价者成交
        min_price = BaseToolUtil.MIN(OPEN, CLOSE)

        pd_temp.loc[:, 'close_next'] = CLOSE.shift(-1 - 1)
        pd_temp.loc[:, 'close_short'] = CLOSE.shift(-self.period_short - 1)
        pd_temp.loc[:, 'close_medium'] = CLOSE.shift(-self.period_medium - 1)
        pd_temp.loc[pd_temp['signal'] == 1, 'profit_next'] = pd_temp['close_next'] / min_price - 1
        pd_temp.loc[pd_temp['signal'] == 1, 'profit_short'] = pd_temp['close_short'] / min_price - 1
        pd_temp.loc[pd_temp['signal'] == 1, 'profit_medium'] = pd_temp['close_medium'] / min_price - 1

        self.pd_data = self.pd_data.join(pd_temp[['profit_next', 'profit_short', 'profit_medium']])
        self.calculate_flag = True

    def income_report(self):
        '''
        收益报表
        :param pd_data:
        :return:
        '''
        if not self.calculate_flag:
            self.calculateProfit()

        pd_deal = self.pd_data.loc[
            self.pd_data['signal'] == 1, ['close', 'profit_next', 'profit_short', 'profit_medium', 'signal']]

        index = ['次日', '5日', '10日']
        total_counts = pd_deal.shape[0]

        print(f"总次数：{total_counts}")

        # 成功个数
        suc_rate_next = pd_deal[pd_deal['profit_next'] > 0].shape[0] / total_counts
        suc_rate_short = pd_deal[pd_deal['profit_short'] > 0].shape[0] / total_counts
        suc_rate_medium = pd_deal[pd_deal['profit_medium'] > 0].shape[0] / total_counts

        income_next = pd_deal['profit_next'].mean()
        income_short = pd_deal['profit_short'].mean()
        income_medium = pd_deal['profit_medium'].mean()

        result_dict = {"成功率": [suc_rate_next, suc_rate_short, suc_rate_medium],
                       "平均收益率": [income_next, income_short, income_medium]
                       }

        return pd.DataFrame(result_dict, index=index)


class LineBonding:
    '''
    均线粘合逻辑判断
    '''

    def bonding1(self, pd_data):
        CLOSE = pd_data['close']
        ma_5 = BaseToolUtil.MA(CLOSE, 5)
        ma_10 = BaseToolUtil.MA(CLOSE, 10)
        ma_20 = BaseToolUtil.MA(CLOSE, 20)
        X1 = BaseToolUtil.ABS(ma_10 / ma_20 - 1) < 0.01

        X2 = BaseToolUtil.ABS(ma_5 / ma_10 - 1) < 0.01

        X3 = BaseToolUtil.ABS(ma_5 / ma_20 - 1) < 0.01

        # 均线掉头向上
        AA = (ma_5 > BaseToolUtil.REF(ma_5)) & (ma_10 > BaseToolUtil.REF(ma_10)) & (ma_5 > ma_10)

        # 长短均线距离绝对值越小越好
        CDJZ = BaseToolUtil.ABS(ma_5 - ma_20) / ma_20 < 0.4

        FLAG = X1 & X2 & X3 & CDJZ
        pd_data.loc[FLAG, '粘合1'] = 1

    def bonding2(self, pd_data):
        CLOSE = pd_data['close']
        ma_5 = BaseToolUtil.MA(CLOSE, 5)
        ma_10 = BaseToolUtil.MA(CLOSE, 10)
        ma_20 = BaseToolUtil.MA(CLOSE, 20)
        # ma_30 = BaseToolUtil.MA(CLOSE, 30)

        util_max = BaseToolUtil.MAX(BaseToolUtil.MAX(ma_5, ma_10), ma_20)
        util_min = BaseToolUtil.MIN(BaseToolUtil.MIN(ma_5, ma_10), ma_20)

        pd_data.loc[(util_max / util_min) < 1.01, '粘合2'] = 1

    def bonding3(self, pd_data):
        CLOSE = pd_data['close']
        V = pd_data['volume']
        ma_5 = BaseToolUtil.MA(CLOSE, 5)
        ma_10 = BaseToolUtil.MA(CLOSE, 10)
        ma_20 = BaseToolUtil.MA(CLOSE, 20)
        # ma_30 = BaseToolUtil.MA(CLOSE, 30)

        unit_max = BaseToolUtil.MAX(BaseToolUtil.MAX(ma_5, ma_10), ma_20)
        unit_min = BaseToolUtil.MIN(BaseToolUtil.MIN(ma_5, ma_10), ma_20)

        A1 = (unit_max / unit_min - 1) < 0.005

        A3 = (V > BaseToolUtil.REF(V) * 1.5) & (V > BaseToolUtil.MA(V, 5))

        FLAG = A1 & A3

        pd_data.loc[FLAG, '粘合3'] = 1

    def breach1(self, pd_data):
        '''
        突破
        '''
        pd_temp = pd_data.copy()
        pct_chg = pd_temp['pctChg']
        OPEN = pd_temp['open']
        CLOSE = pd_temp['close']

        ma_5 = BaseToolUtil.MA(CLOSE, 5)
        ma_10 = BaseToolUtil.MA(CLOSE, 10)
        ma_20 = BaseToolUtil.MA(CLOSE, 20)
        ma_30 = BaseToolUtil.MA(CLOSE, 30)
        ma_60 = BaseToolUtil.MA(CLOSE, 60)
        ma_120 = BaseToolUtil.MA(CLOSE, 120)
        ma_250 = BaseToolUtil.MA(CLOSE, 250)

        pd_temp.loc[:, 'ma_5_in'] = BaseToolUtil.IF(
            ma_5.between(BaseToolUtil.MIN(OPEN, CLOSE), BaseToolUtil.MAX(OPEN, CLOSE)), 1, 0)
        pd_temp.loc[:, 'ma_10_in'] = BaseToolUtil.IF(
            ma_10.between(BaseToolUtil.MIN(OPEN, CLOSE), BaseToolUtil.MAX(OPEN, CLOSE)), 1, 0)
        pd_temp.loc[:, 'ma_20_in'] = BaseToolUtil.IF(
            ma_20.between(BaseToolUtil.MIN(OPEN, CLOSE), BaseToolUtil.MAX(OPEN, CLOSE)), 1, 0)
        pd_temp.loc[:, 'ma_30_in'] = BaseToolUtil.IF(
            ma_30.between(BaseToolUtil.MIN(OPEN, CLOSE), BaseToolUtil.MAX(OPEN, CLOSE)), 1, 0)
        pd_temp.loc[:, 'ma_60_in'] = BaseToolUtil.IF(
            ma_60.between(BaseToolUtil.MIN(OPEN, CLOSE), BaseToolUtil.MAX(OPEN, CLOSE)), 1, 0)
        pd_temp.loc[:, 'ma_120_in'] = BaseToolUtil.IF(
            ma_120.between(BaseToolUtil.MIN(OPEN, CLOSE), BaseToolUtil.MAX(OPEN, CLOSE)), 1, 0)
        pd_temp.loc[:, 'ma_250_in'] = BaseToolUtil.IF(
            ma_250.between(BaseToolUtil.MIN(OPEN, CLOSE), BaseToolUtil.MAX(OPEN, CLOSE)), 1, 0)
        pd_temp.loc[:, 'ma_in_count'] = pd_temp['ma_5_in'] + pd_temp['ma_10_in'] + pd_temp['ma_20_in'] + pd_temp[
            'ma_30_in'] + pd_temp['ma_60_in'] + pd_temp['ma_120_in'] + pd_temp['ma_250_in']

        # util_max = BaseToolUtil.MAX(BaseToolUtil.MAX(ma_5, ma_10), ma_20)
        # util_min = BaseToolUtil.MIN(BaseToolUtil.MIN(ma_5, ma_10), ma_20)
        FLAG = (CLOSE / OPEN - 1 > 0.05) & (pd_temp['ma_in_count'] >= 3)
        pd_temp.loc[FLAG, '突破1'] = 1
        return pd_data.join(pd_temp[['突破1']])

    def breach2(self, pd_data):
        '''
        突破(废弃)
        '''
        pct_chg = pd_data['pctChg']
        OPEN = pd_data['open']
        CLOSE = pd_data['close']

        ma_30 = BaseToolUtil.MA(CLOSE, 30)
        ma_60 = BaseToolUtil.MA(CLOSE, 60)
        ma_90 = BaseToolUtil.MA(CLOSE, 90)
        ma_250 = BaseToolUtil.MA(CLOSE, 250)

        util_max = BaseToolUtil.MAX(BaseToolUtil.MAX(ma_30, ma_60), ma_90)
        util_min = BaseToolUtil.MIN(BaseToolUtil.MIN(ma_30, ma_60), ma_90)
        util_ave = (ma_30 + ma_60 + ma_90) / 3

        FLAG = (util_max / util_min < 1.05) & (CLOSE > util_ave * 1.04) & (CLOSE < util_ave * 1.15) & (
                ma_250 > BaseToolUtil.REF(ma_250)) & (util_ave > ma_250) & (CLOSE / BaseToolUtil.REF(CLOSE) > 1.05)
        pd_data.loc[FLAG, '突破2'] = 1


class LeastSquareMethod:
    '''
    https://blog.csdn.net/tailonh/article/details/113887752
        1.做最小二乘拟合，把序列拟合成一条直线;
        2.根据直线的斜率k可以得知序列的主要走势：
        例如：(1)k > 0.1763 上升  (2) k < -0.1763 下降 (3)其他
        3.然后计算序列各点到直线的距离（和方差一样）
        设定一个阈值L，统计超过L的点数目，点数目越多说明序列震荡越厉害

        找转折点  https://www.coder.work/article/4910760
        单调递增(减) https://www.codenong.com/28093365/
    '''

    # 趋势判断
    @staticmethod
    def fit_slope(data):  # 拟合曲线
        order = 1
        index = [i for i in range(1, len(data) + 1)]  # x轴坐标
        coeffs = np.polyfit(index, list(data), order)  # 曲线拟合
        # k = coeffs[0] # 斜率
        return coeffs

    @staticmethod
    def judge_slope(coeffs, data, degree, shake=1):
        tan_k = math.tan(degree * math.pi / 180)  # 注意弧度转化
        # print(coeffs[0])
        # print(tan_k)
        if coeffs[0] >= tan_k:
            return TrendEnum.UP
        elif coeffs[0] <= -tan_k:
            return TrendEnum.DOWN
        else:
            return LeastSquareMethod.get_shake(coeffs, data, shake)

    @staticmethod
    def get_shake(coeffs, data, shake):
        count = 0
        for i, d in enumerate(data):  # i+1相当于横坐标，从1开始
            y = np.polyval(coeffs, i + 1)
            count += (y - d) ** 2
        # print("count: ",count)
        if count > shake:
            return TrendEnum.WAVE
        else:
            return TrendEnum.STABLE

    @staticmethod
    def judge_trend(data, degree=1, shake=1) -> TrendEnum:
        '''
        判断一组数据的趋势
        :param data:
        :param degree:
        :param shake:
        :return: -3：暴跌；-2：下降；-1：波动；0：转折点；1：平稳；2：上升；3：暴力拉升
        '''
        coeffs = LeastSquareMethod.fit_slope(data)
        return LeastSquareMethod.judge_slope(coeffs, data, degree, shake)

    @staticmethod
    def judge_position(pd_series, period_short=5, period_medium=10, period_long=20):
        last = pd_series[-1]
        # last_short = pd_series[-period_short]
        # last_medium = pd_series[-period_medium]
        # last_long = pd_series[-period_long]
        trend_last = LeastSquareMethod.judge_trend(pd_series[-period_short: -1])
        trend_medium = LeastSquareMethod.judge_trend(pd_series[-period_medium: -period_short])
        trend_long = LeastSquareMethod.judge_trend(pd_series[-period_long: -period_medium])
        trend_whole = LeastSquareMethod.judge_trend(pd_series[-period_long: -1])
        #  下跌-2 ,   上涨2,     横盘:-1,1

        print(f'统计周期:{period_long},整体趋势是:{trend_whole.msg}')
        highest_long = Extremum.highest1(pd_series, period_long)
        lowest_long = Extremum.lowest1(pd_series, period_long)
        chg_high = ProfitUtil.calculationChg(last, highest_long[1])
        chg_low = ProfitUtil.calculationChg(last, lowest_long[1])
        print(f'统计周期:{period_long},当前点位距最低点已经上涨{chg_low},距最高点回落{chg_high}')

        print(f'最近{period_short}日趋势是{trend_last.msg}')
        print(f'第{period_short}-{period_medium}日的趋势是{trend_medium.msg}')
        print(f'第{period_medium}-{period_long}日的趋势是{trend_long.msg}')

    @staticmethod
    def judge_position1(pd_data, colume, period_short=5, period_medium=10, period_long=20):
        pd_series = pd_data[colume]
        pd_temp = pd.DataFrame()
        count = 0
        for index, row in pd_data.iterrows():
            count += 1
            if count < period_long:
                continue
            tail_short = pd_series.loc[:index].tail(period_short)
            tail_medium = pd_series.loc[:index].tail(period_medium)
            tail_long = pd_series.loc[:index].tail(period_long)

            trend_last = LeastSquareMethod.judge_trend(tail_short)
            trend_medium = LeastSquareMethod.judge_trend(tail_medium[-period_medium: -period_short])
            trend_long = LeastSquareMethod.judge_trend(tail_long[-period_long: -period_medium])
            trend_whole_long = LeastSquareMethod.judge_trend(tail_long)
            trend_whole_medium = LeastSquareMethod.judge_trend(tail_medium)
            #  下跌-2 ,   上涨2,     横盘:-1,1

            last = tail_long[index]
            highest_long = Extremum.highest1(tail_long, period_long)
            lowest_long = Extremum.lowest1(tail_long, period_long)
            chg_high = ProfitUtil.calculationChg(last, highest_long[1])
            chg_low = ProfitUtil.calculationChg(last, lowest_long[1])

            pd_temp.loc[index, f'近{period_short}日整体趋势'] = trend_last.msg
            pd_temp.loc[index, f'近{period_medium}日整体趋势'] = trend_whole_medium.msg
            pd_temp.loc[index, f'近{period_long}日整体趋势'] = trend_whole_long.msg
            pd_temp.loc[index, f'近{period_short}-{period_medium}日整体趋势'] = trend_medium.msg
            pd_temp.loc[index, f'近{period_medium}-{period_long}日整体趋势'] = trend_long.msg
            pd_temp.loc[index, f'近{period_long}日当前点位距最低点已经上涨'] = chg_low
            pd_temp.loc[index, f'近{period_long}日当前点位距最高点回落'] = chg_high

        return pd.concat([pd_data, pd_temp], axis=1)


class LifelineHander:
    def __init__(self, pd_data):
        self.pd_data = pd_data
        self.pd_temp = pd_data.copy()

        self.pd_temp = self.pd_temp.tail(500)
        self.drift = 2

    def create_line(self):
        VolumeHandle(self.pd_temp).rising_band()
        self.pd_temp.loc[self.pd_temp['chg_vol_signal'] == 1, 'lifeline'] = self.pd_temp['close']
        self.create_lifeline()

        sort_idx = self.pd_lifeline.sort_index()
        self.pd_temp.drop('lifeline', axis=1, inplace=True)
        self.pd_temp.loc[self.pd_lifeline.index, 'line'] = np.nan
        i = 0
        line_size = sort_idx.shape[0]
        self.pd_temp = self.pd_temp.tail(200)
        for index_num in range(self.pd_temp.shape[0]):
            row = self.pd_temp.iloc[index_num]
            index = row.name

            yes_row = self.pd_temp.iloc[index_num - 1]
            yes_lifeline = yes_row['line']

            sort_date = sort_idx.index[i]

            if (i == 0) & (index.value <= sort_date.value):
                continue

            if index.value > sort_date.value:
                if (i < line_size - 2) & (index.value > sort_idx.index[i + 1].value):
                    i += 1

            lines_price = self.filter_lines_by_price(sort_idx.head(i + 1), row['close'])

            high_line = lines_price[3]
            low_line = lines_price[1]

            lines = []
            if not math.isnan(yes_lifeline):
                lines.append(yes_lifeline)

            if row['pctChg'] >= 0:
                if high_line:
                    lines.append(high_line)

                msg = self.price_line_position(row, lines)
                if msg:
                    self.pd_temp.loc[index, 'signal'] = msg
                self.pd_temp.loc[index, 'line'] = high_line
            else:
                if low_line:
                    lines.append(low_line)

                msg = self.price_line_position(row, lines)
                if msg:
                    self.pd_temp.loc[index, 'signal'] = msg
                self.pd_temp.loc[index, 'line'] = low_line

        print(self.pd_temp)
        # lifeList = []
        # to_dict = sort_values.to_dict()
        # lifeline_dict = to_dict.get('lifeline')
        # for key, value in lifeline_dict.items():
        #     lifeline = StockLifeline()
        #     setattr(lifeline, 'stock_code', '000868')
        #     setattr(lifeline, 'stock_name', '安凯客车')
        #     setattr(lifeline, 'line_code', CommonUtil.get_uuid())
        #     setattr(lifeline, 'line_type', LifelineTypeEnum.HORIZON.code)
        #     setattr(lifeline, 'line_mode', LineModeEnum.INCREASE.code)
        #     setattr(lifeline, 'point_1_time', key)
        #     setattr(lifeline, 'create_time', key)
        #     setattr(lifeline, 'point_1_price', value)
        #     setattr(lifeline, 'create_time', datetime.now())
        #     lifeList.append(lifeline)
        #
        # # StockLifelineDao().saveList(lifeList)
        #
        # print(sort_values)

    def create_lifeline(self, drift=0.05):
        '''
        过滤生命线
        :param drift: 生命线之间的间距要>=drift
        :return:
        '''
        self.pd_lifeline = pd.DataFrame(
            self.pd_temp.loc[(self.pd_temp['lifeline'].notnull()), 'lifeline'])
        self.pd_lifeline.drop_duplicates(keep='first', inplace=True)
        self.pd_lifeline.sort_values(by='lifeline', inplace=True)

        while True:
            self.pd_lifeline.loc[:, 'pct_chg'] = self.pd_lifeline.pct_change()
            condition = (self.pd_lifeline['pct_chg'] < drift) & (
                    self.pd_lifeline['pct_chg'].shift(1) > drift)
            aa_any = condition.any()
            if not aa_any:
                break

            self.pd_lifeline.drop(self.pd_lifeline[condition].index, inplace=True)
            self.pd_lifeline.drop('pct_chg', axis=1, inplace=True)

    def in_line_scope(self, cur_price, line_price):
        '''
             判断价位是否在生命线范围内范围
        '''
        if not line_price:
            return False
        chg = BaseToolUtil.ABS(ProfitUtil.calculationChg(cur_price, line_price))
        return chg <= self.drift * 5

    def price_line_position(self, day_date, price_lines, drift=0.03):
        '''
        todo 将当天open,close,high,low 传进来   5.18
        '''
        if len(price_lines) == 0:
            return None
        result_msg = []
        for line_price in price_lines:

            # 生命线上沿
            line_upper = line_price * (1 + drift)
            # 生命线下沿
            line_lower = line_price * (1 - drift)

            _open = day_date['open']
            _cur = day_date['close']
            _pctChg = day_date['pctChg']
            # _high = day_date['high']
            # _low = day_date['low']
            if _pctChg > 0:
                if _cur > line_upper:
                    result_msg.append(LinePositionEnum.RISE.msg)
                elif (_cur >= line_lower) & (_cur <= line_upper):
                    result_msg.append(LinePositionEnum.PRESSURE.msg)
            else:
                if _cur < line_lower:
                    result_msg.append(LinePositionEnum.FALL.msg)
                elif (_cur >= line_lower) & (_cur <= line_upper):
                    result_msg.append(LinePositionEnum.SUPPORT.msg)

        if len(result_msg) > 0:
            return ','.join(set(result_msg))
        return ''

    def filter_lines_by_price(self, pd_sort, cur_price):
        '''
             过滤当前价位附近的生命线
        '''
        pd_line_temp = pd_sort.sort_values(by='lifeline')
        closest_index = (pd_line_temp['lifeline'] - cur_price).abs().argsort()[0]
        closest_val = pd_line_temp.iloc[closest_index]['lifeline']
        if cur_price > closest_val:
            left_idx = closest_index
            left_val = closest_val
            right_idx = closest_index + 1
            if right_idx >= pd_line_temp.shape[0]:
                return left_idx, left_val, None, None
            right_val = pd_line_temp.iloc[right_idx]['lifeline']
            return left_idx, left_val, right_idx, right_val
        elif cur_price <= closest_val:
            right_idx = closest_index
            right_val = closest_val
            left_idx = closest_index - 1
            if left_idx < 0:
                return None, None, right_idx, right_val
            left_val = pd_line_temp.iloc[left_idx]['lifeline']
            return left_idx, left_val, right_idx, right_val

    def breakthrough_line(self):
        '''
            突破生命线
        '''

        pass
